import logging
from typing import NoReturn

from sqlalchemy import exists, func, select
from sqlalchemy.exc import DBAPIError, IntegrityError

from src.application.common.exceptions import RepoError
from src.application.user import dto
from src.application.user.exceptions import (
    UserEmailAlreadyExist,
    UserEmailNotExist,
    UserIdAlreadyExist,
    UserIdNotExist,
    UserTgIdNotExist,
)
from src.application.user.interfaces import UserReader
from src.application.user.interfaces.reader import GetUsersFilters
from src.application.user.interfaces.repo import UserRepo
from src.domain.common.constants import Empty, OrderBy
from src.domain.user.aggregates import UserAggregate
from src.domain.user.value_objects import TelegramUserId, UserId
from src.infrastructure.db.converters.user import (
    convert_db_model_to_active_user_dto,
    convert_db_model_to_user_dto,
    convert_db_model_to_user_entity,
    convert_user_entity_to_db_model,
)
from src.infrastructure.db.exception_mapper import exception_mapper
from src.infrastructure.db.models import User
from src.infrastructure.db.repositories.base import SQLAlchemyRepo

logger = logging.getLogger(__name__)


class UserReaderImpl(SQLAlchemyRepo, UserReader):
    @exception_mapper
    async def get_user_by_id(self, user_id: int) -> dto.UserDTO:
        user: User | None = await self._session.scalar(
            select(User).where(
                User.id == user_id,
            )
        )
        if user is None:
            raise UserIdNotExist(user_id)

        return convert_db_model_to_active_user_dto(user)

    @exception_mapper
    async def get_user_by_tg_id(self, user_tg_id: int) -> dto.UserDTO:
        user: User | None = await self._session.scalar(
            select(User).where(
                User.telegram_id == user_tg_id,
            )
        )
        if user is None:
            raise UserTgIdNotExist(user_tg_id)

        return convert_db_model_to_active_user_dto(user)

    @exception_mapper
    async def get_user_by_email(self, email: str) -> dto.UserDTO:
        user: User | None = await self._session.scalar(
            select(User).where(
                User.email == email,
            )
        )
        if user is None:
            raise UserEmailNotExist(email)

        return convert_db_model_to_active_user_dto(user)

    @exception_mapper
    async def get_users(self, filters: GetUsersFilters) -> list[dto.UserDTOs]:
        query = select(User)

        if filters.order is OrderBy.DESC:
            query = query.order_by(User.id.desc())
        else:
            query = query.order_by(User.id.asc())

        if filters.is_registered is not Empty.UNSET:
            query = query.where(User.is_active == filters.is_registered)

        if filters.offset is not Empty.UNSET:
            query = query.offset(filters.offset)
        if filters.limit is not Empty.UNSET:
            query = query.limit(filters.limit)

        result = await self._session.scalars(query)
        users: list[User] = list(result)

        return [convert_db_model_to_user_dto(user) for user in users]

    async def get_users_count(self, is_active: bool | Empty = Empty.UNSET) -> int:
        query = select(func.count(User.id))

        if is_active is not Empty.UNSET:
            query = query.where(User.is_active == is_active)

        users_count: int = await self._session.scalar(query)
        return users_count or 0


class UserRepoImpl(SQLAlchemyRepo, UserRepo):
    @exception_mapper
    async def acquire_user_by_id(self, user_tg_id: UserId) -> UserAggregate:
        user: User | None = await self._session.scalar(
            select(User)
            .where(
                User.telegram_id == user_tg_id.value,
            )
            .with_for_update()
        )
        if user is None:
            raise UserIdNotExist(user_tg_id.value)

        return convert_db_model_to_user_entity(user)

    @exception_mapper
    async def create_user(self, user: UserAggregate) -> None:
        db_user = convert_user_entity_to_db_model(user)
        self._session.add(db_user)
        try:
            await self._session.flush((db_user,))
        except IntegrityError as err:
            self._parse_error(err, user)

    @exception_mapper
    async def update_user(self, user: UserAggregate) -> None:
        db_user = convert_user_entity_to_db_model(user)
        print(db_user)
        try:
            await self._session.merge(db_user)
        except IntegrityError as err:
            self._parse_error(err, user)

    @exception_mapper
    async def check_user_exists(self, user_tg_id: TelegramUserId) -> bool:
        """Проверяем, существует ли юзер с таким tg id"""
        user_exists: bool = await self._session.scalar(select(exists().where(User.telegram_id == user_tg_id.value)))
        return user_exists

    def _parse_error(self, err: DBAPIError, user: UserAggregate) -> NoReturn:
        logger.info(err.__cause__.__cause__.constraint_name)
        match err.__cause__.__cause__.constraint_name:  # type: ignore
            case "pk_users":
                raise UserIdAlreadyExist(user.tg_id.value) from err
            case "uq_user_email":
                raise UserEmailAlreadyExist(str(user.email)) from err
            case _:
                raise RepoError from err
